// ===================== Fall 2022 EECS 493 Assignment 3 =====================
// This starter code provides a structure and helper functions for implementing
// the game functionality. It is a suggestion meant to help you, and you are not
// required to use all parts of it. You can (and should) add additional functions
// as needed or change existing functions.

// ==================================================
// ============ Page Scoped Globals Here ============
// ==================================================

// Div Handlers
let game_window;
let game_screen;
let onScreenAsteroid;
let main_menu;
let setting;
let tutorial;
let game_over;
let get_ready;

// Difficulty Helpers
let astProjectileSpeed = 3;          // easy: 1, norm: 3, hard: 5

// Game Object Helpers
var rocket;
var rocketWrap;
var isGameStart = false;
let currentAsteroid = 1;
let difficulty = {
  easy: {danger: 10, spawn_Rate: 1000, astProjectileSpeed: 1},
  norm: {danger: 20, spawn_Rate: 800, astProjectileSpeed: 3},
  hard: {danger: 30, spawn_Rate: 600, astProjectileSpeed: 5},
};
let isTheFirstPlay = true;
let difficultyStatus = "norm";
let score = 0;
let danger = 20;
let level = 1;
let AST_OBJECT_REFRESH_RATE = 15;
let spawn_Rate = 800;                // easy: 1000, norm: 800, hard: 600
let maxPersonPosX = 1218;
let maxPersonPosY = 658;
let PERSON_SPEED = 5;                // Speed of the person
let vaccineOccurrence = 20000;       // Vaccine spawns every 20 seconds
let vaccineGone = 5000;              // Vaccine disappears in 5 seconds
let maskOccurrence = 15000;          // Masks spawn every 15 seconds
let maskGone = 5000;                 // Mask disappears in 5 seconds
var asteroidArr = [];

// Movement Helpers
var LEFT = false;
var RIGHT = false;
var UP = false;
var DOWN = false;
var touched = false;

// ==============================================
// ============ Functional Code Here ============
// ==============================================

// Main
$(document).ready(function () {
  // ====== Startup ====== 
  game_window = $('.game-window');
  game_screen = $("#actual_game");
  onScreenAsteroid = $('.curAstroid');

  // TODO: ADD MORE
  // spawn(); // Example: Spawn an asteroid that travels from one border to another

  // MY CODE
  main_menu = $('#main_menu');
  setting = $('#settings');
  tutorial = $('#tutorial');
  game_over = $('#game_over');
  rocket = $('#rocket').hide();
  get_ready = $('#get_ready');

  showCurPage([main_menu]);

  // button click
  bindAllBtnClick();
  // game start
  // gameStart();
});

// TODO: ADD YOUR FUNCTIONS HERE
var timer_portal;
var timer_shield;
var timer_rocket;
var timer_spawn;

function bindAllBtnClick() {
  $('#main_setting_btn').click(function () {
    showCurPage([main_menu, setting])
  })
  var slider = document.getElementById("myRange");
  var output = document.getElementById("volume_num");
  output.innerHTML = slider.value;
  slider.oninput = function () {
    output.innerHTML = this.value;
    $('#audio').volume = this.value;
  }
  $('.easy').click(function () {
    $('#difficuly_btn button').removeClass("active");
    $('.easy').addClass("active");
    difficultyStatus = "easy"
  })
  $('.norm').click(function () {
    $('#difficuly_btn button').removeClass("active");
    $('.norm').addClass("active");
    difficultyStatus = "norm"
  })
  $('.hard').click(function () {
    $('#difficuly_btn button').removeClass("active");
    $('.hard').addClass("active");
    difficultyStatus = "hard"
  })
  $('.close_btn').click(() => {
    showCurPage([main_menu])
  })
  $('#start_over_btn').click(() => {
    isTheFirstPlay = false;
    showCurPage([main_menu])
  })
  $('#playGame_btn').click(() => {
    if (isTheFirstPlay) {
      showCurPage([tutorial])
    } else {
      showCurPage([game_screen, get_ready], true)
      $('#danger_num').text(difficulty[difficultyStatus].danger);
      $('#score_num').text(0);
      $('#level_num').text(1);
    }
  })
  $('#tutorial_start_btn').click(() => {
    showCurPage([game_screen, get_ready], true)
    // console.log(difficulty[difficultyStatus].danger)
    // console.log(difficultyStatus)
    $('#danger_num').text(difficulty[difficultyStatus].danger);
    $('#score_num').text(0);
    $('#level_num').text(1);
  })
}

function gameStart() {

  rocketWrap = $('#rocket_wrap');
  // change game status
  isGameStart = true;

  // show Game Page
  showCurPage([game_screen]);

  // init
  spawn_Rate = difficulty[difficultyStatus].spawn_Rate
  astProjectileSpeed = difficulty[difficultyStatus].astProjectileSpeed
  danger = difficulty[difficultyStatus].danger;
  level = 1;
  score = 0;
  setInterval(() => {
    score += 40;
    $('#score_num').text(score);
  }, 500)

  // set all timer
  timer_portal = portal();
  timer_shield = shield();
  timer_rocket = rocketInit();
  timer_spawn = keepAddSpawn();
}

function gameEnd() {
  // change game status
  isGameStart = false;

  // stop all timer
  for (let i = 1; i <= 10000; i++) {
    clearInterval(i);
  }
  // play music
  let $audio = $("#audio")[0];
  $audio.src = "src/audio/die.mp3"
  $audio.play()
  $('#game_over_score').text(score);
  // show game over page
  setTimeout(() => {
    asteroidArr.map(item => item.id.remove());
    showCurPage([game_over])
  }, 2000);
}

function keepAddSpawn() {
  return setInterval(() => {
    spawn();
  }, spawn_Rate);
}

function stopAddSpawn(timer) {
  clearInterval(timer);
}

function showCurPage(needToShowPage, get_read = false) {
  // hide all page
  game_screen.hide();
  main_menu.hide();
  setting.hide();
  tutorial.hide();
  game_over.hide();
  get_ready.hide();
  // show cur page
  needToShowPage.map(item => item.show());
  if (get_read) {
    setTimeout(() => {
      gameStart()
    }, 3000)
  }
}

// rocket about
function rocketInit() {
  rocket.show();
  rocket.css({transform: "translate(-50%, -50%)",top: "50%", left: "50%"})
  return setInterval(() => {
    if (isGameStart) {
      if (UP && rocket.offset().top - rocketWrap.offset().top) {
        rocket.css({top: (index, value) => (parseInt(value) - PERSON_SPEED) > 13 ? parseInt(value) - PERSON_SPEED : 13})
      }
      if (DOWN && rocket.offset().top - rocketWrap.offset().top + rocket.height() < rocketWrap.height()) {
        rocket.css({top: (index, value) => parseInt(value) + PERSON_SPEED})
      }
      if (LEFT && rocket.offset().left - rocketWrap.offset().left) {
        rocket.css({left: (index, value) => parseInt(value) - PERSON_SPEED})
      }
      if (RIGHT && rocket.offset().left - rocketWrap.offset().left + rocket.width() < rocketWrap.width()) {
        rocket.css({left: (index, value) => parseInt(value) + PERSON_SPEED})
      }
      if (rocket.shield) {
        rocket.css({"backgroundSize": "90px 90px", width: "90px", height: "90px"})
      }
    }
  }, 15)
}

// portal about
function portal() {
  return setInterval(showPortal, vaccineOccurrence) // 20s
}

function showPortal() {
  let curPort = $('.curPort');
  let x = getRandomNumber(0, 1280 - 80) + 'px';
  let y = getRandomNumber(0, 720 - 80) + 'px';
  let objectString = `<div id='portal'> <img src = 'src/port.gif' style='top: ${y};left: ${x};' /></div>`;
  curPort.append(objectString);
  let t = setInterval(() => {
    try {
      isColliding($('#portal img'), rocket)
    } catch (e) {
      clearInterval(t)
    }
    if (isColliding($('#portal img'), rocket)) {
      let $audio = $("#audio")[0];
      $audio.src = "src/audio/collect.mp3"
      $audio.play();
      addScore();
      $('#portal').remove()
      clearInterval(t);
    }
  }, 15)
  // 5s remove port
  setTimeout(function () {
    $('#portal').remove()
    clearInterval(t)
  }, vaccineGone)
}

function addScore() {
  level += 1
  danger = danger * 2;
  astProjectileSpeed = astProjectileSpeed * 1.2;
  // console.log(score, level, danger)
  $('#danger_num').text(danger);
  $('#level_num').text(level);
}

// shield about
function shield() {
  return setInterval(showShield, maskOccurrence) // 15s
}

function showShield() {
  let curShield = $('.curShield');
  let x = getRandomNumber(0, 1280 - 80) + 'px';
  let y = getRandomNumber(0, 720 - 80) + 'px';
  let objectString = `<div id='shield'> <img src = 'src/shield.gif' style='top: ${y};left: ${x};' /></div>`;
  curShield.append(objectString);
  let t = setInterval(() => {
    if (isColliding($('#shield img'), rocket)) {
      rocket.shield = true;
      // play music
      let $audio = $("#audio")[0];
      $audio.src = "src/audio/collect.mp3"
      $audio.play()
      $('#shield').remove();
      rocket.css("backgroundImage", "url(src/player/player.gif)")
      clearInterval(t);
    }
  }, 15);

  // 5s remove shield
  setTimeout(function () {
    $('#shield').remove();
    clearInterval(t);
  }, maskGone)
}

// Keydown event handler
document.onkeydown = function (e) {
  if (e.key == 'ArrowLeft') {
    LEFT = true;
    rocket.css("backgroundImage", rocket.shield ? "url(src/player/player_shielded_left.gif)" : "url(src/player/player_left.gif)");
  }
  if (e.key == 'ArrowRight') {
    RIGHT = true;
    rocket.css("backgroundImage", rocket.shield ? "url(src/player/player_shielded_right.gif)" : "url(src/player/player_right.gif)");
  }
  if (e.key == 'ArrowUp') {
    UP = true;
    rocket.css("backgroundImage", rocket.shield ? "url(src/player/player_shielded_up.gif)" : "url(src/player/player_up.gif)");
  }
  if (e.key == 'ArrowDown') {
    DOWN = true;
    rocket.css("backgroundImage", rocket.shield ? "url(src/player/player_shielded_down.gif)" : "url(src/player/player_down.gif)");
  }
}

// Keyup event handler
document.onkeyup = function (e) {
  if (e.key == 'ArrowLeft') LEFT = false;
  if (e.key == 'ArrowRight') RIGHT = false;
  if (e.key == 'ArrowUp') UP = false;
  if (e.key == 'ArrowDown') DOWN = false;
  if (isGameStart) rocket.css("backgroundImage", rocket.shield ? "url(src/player/player_shielded.gif)" : "url(src/player/player.gif)")
}

// Starter Code for randomly generating and moving an asteroid on screen
// Feel free to use and add additional methods to this class
class Asteroid {
  // constructs an Asteroid object
  constructor() {
    /*------------------------Public Member Variables------------------------*/
    // create a new Asteroid div and append it to DOM so it can be modified later
    let objectString = "<div id = 'a-" + currentAsteroid + "' class = 'curAstroid' > <img src = 'src/asteroid.png'/></div>";
    onScreenAsteroid.append(objectString);
    // select id of this Asteroid
    this.id = $('#a-' + currentAsteroid);
    currentAsteroid++; // ensure each Asteroid has its own id
    // current x, y position of this Asteroid
    this.cur_x = 0; // number of pixels from right
    this.cur_y = 0; // number of pixels from top

    /*------------------------Private Member Variables------------------------*/
    // member variables for how to move the Asteroid
    this.x_dest = 0;
    this.y_dest = 0;
    // member variables indicating when the Asteroid has reached the boarder
    this.hide_axis = 'x';
    this.hide_after = 0;
    this.sign_of_switch = 'neg';
    // spawn an Asteroid at a random location on a random side of the board
    this.#spawnAsteroid();
  }

  // Requires: called by the user
  // Modifies:
  // Effects: return true if current Asteroid has reached its destination, i.e., it should now disappear
  //          return false otherwise
  hasReachedEnd() {
    if (this.hide_axis == 'x') {
      if (this.sign_of_switch == 'pos') {
        if (this.cur_x > this.hide_after) {
          return true;
        }
      } else {
        if (this.cur_x < this.hide_after) {
          return true;
        }
      }
    } else {
      if (this.sign_of_switch == 'pos') {
        if (this.cur_y > this.hide_after) {
          return true;
        }
      } else {
        if (this.cur_y < this.hide_after) {
          return true;
        }
      }
    }
    return false;
  }

  // Requires: called by the user
  // Modifies: cur_y, cur_x
  // Effects: move this Asteroid 1 unit in its designated direction
  updatePosition() {
    // ensures all asteroids travel at current level's speed
    this.cur_y += this.y_dest * astProjectileSpeed;
    this.cur_x += this.x_dest * astProjectileSpeed;
    // update asteroid's css position
    this.id.css('top', this.cur_y);
    this.id.css('right', this.cur_x);
  }

  // Requires: this method should ONLY be called by the constructor
  // Modifies: cur_x, cur_y, x_dest, y_dest, num_ticks, hide_axis, hide_after, sign_of_switch
  // Effects: randomly determines an appropriate starting/ending location for this Asteroid
  //          all asteroids travel at the same speed
  #spawnAsteroid() {
    // REMARK: YOU DO NOT NEED TO KNOW HOW THIS METHOD'S SOURCE CODE WORKS
    let x = getRandomNumber(0, 1280);
    let y = getRandomNumber(0, 720);
    let floor = 784;
    let ceiling = -64;
    let left = 1344;
    let right = -64;
    let major_axis = Math.floor(getRandomNumber(0, 2));
    let minor_aix = Math.floor(getRandomNumber(0, 2));
    let num_ticks;

    if (major_axis == 0 && minor_aix == 0) {
      this.cur_y = floor;
      this.cur_x = x;
      let bottomOfScreen = game_screen.height();
      num_ticks = Math.floor((bottomOfScreen + 64) / astProjectileSpeed);

      this.x_dest = (game_screen.width() - x);
      this.x_dest = (this.x_dest - x) / num_ticks + getRandomNumber(-.5, .5);
      this.y_dest = -astProjectileSpeed - getRandomNumber(0, .5);
      this.hide_axis = 'y';
      this.hide_after = -64;
      this.sign_of_switch = 'neg';
    }
    if (major_axis == 0 && minor_aix == 1) {
      this.cur_y = ceiling;
      this.cur_x = x;
      let bottomOfScreen = game_screen.height();
      num_ticks = Math.floor((bottomOfScreen + 64) / astProjectileSpeed);

      this.x_dest = (game_screen.width() - x);
      this.x_dest = (this.x_dest - x) / num_ticks + getRandomNumber(-.5, .5);
      this.y_dest = astProjectileSpeed + getRandomNumber(0, .5);
      this.hide_axis = 'y';
      this.hide_after = 784;
      this.sign_of_switch = 'pos';
    }
    if (major_axis == 1 && minor_aix == 0) {
      this.cur_y = y;
      this.cur_x = left;
      let bottomOfScreen = game_screen.width();
      num_ticks = Math.floor((bottomOfScreen + 64) / astProjectileSpeed);

      this.x_dest = -astProjectileSpeed - getRandomNumber(0, .5);
      this.y_dest = (game_screen.height() - y);
      this.y_dest = (this.y_dest - y) / num_ticks + getRandomNumber(-.5, .5);
      this.hide_axis = 'x';
      this.hide_after = -64;
      this.sign_of_switch = 'neg';
    }
    if (major_axis == 1 && minor_aix == 1) {
      this.cur_y = y;
      this.cur_x = right;
      let bottomOfScreen = game_screen.width();
      num_ticks = Math.floor((bottomOfScreen + 64) / astProjectileSpeed);

      this.x_dest = astProjectileSpeed + getRandomNumber(0, .5);
      this.y_dest = (game_screen.height() - y);
      this.y_dest = (this.y_dest - y) / num_ticks + getRandomNumber(-.5, .5);
      this.hide_axis = 'x';
      this.hide_after = 1344;
      this.sign_of_switch = 'pos';
    }
    // show this Asteroid's initial position on screen
    this.id.css("top", this.cur_y);
    this.id.css("right", this.cur_x);
    // normalize the speed s.t. all Asteroids travel at the same speed
    let speed = Math.sqrt((this.x_dest) * (this.x_dest) + (this.y_dest) * (this.y_dest));
    this.x_dest = this.x_dest / speed;
    this.y_dest = this.y_dest / speed;
  }
}

// Spawns an asteroid travelling from one border to another
function spawn() {
  let asteroid = new Asteroid();
  asteroidArr.push(asteroid);
  setTimeout(spawn_helper(asteroid), 0);
}

function spawn_helper(asteroid) {
  let astermovement = setInterval(function () {
    // update asteroid position on screen
    asteroid.updatePosition();

    // game over
    if (isColliding(asteroid.id, rocket)) {
      if (rocket.shield) {
        asteroid.id.remove();
        clearInterval(astermovement);
        rocket.shield = false
      } else {
        clearInterval(astermovement);
        astProjectileSpeed = 0;
        gameEnd();
      }
    }
    // determine whether asteroid has reached its end position, i.e., outside the game border
    if (asteroid.hasReachedEnd()) {
      asteroid.id.remove();
      clearInterval(astermovement);
    }
  }, AST_OBJECT_REFRESH_RATE);
}

//===================================================

// ==============================================
// =========== Utility Functions Here ===========
// ==============================================

// Are two elements currently colliding?
function isColliding(o1, o2) {
  return isOrWillCollide(o1, o2, 0, 0);
}

// Will two elements collide soon?
// Input: Two elements, upcoming change in position for the moving element
function willCollide(o1, o2, o1_xChange, o1_yChange) {
  return isOrWillCollide(o1, o2, o1_xChange, o1_yChange);
}

// Are two elements colliding or will they collide soon?
// Input: Two elements, upcoming change in position for the moving element
// Use example: isOrWillCollide(paradeFloat2, person, FLOAT_SPEED, 0)
function isOrWillCollide(o1, o2, o1_xChange, o1_yChange) {
  const o1D = {
    'left': o1.offset().left + o1_xChange,
    'right': o1.offset().left + o1.width() + o1_xChange,
    'top': o1.offset().top + o1_yChange,
    'bottom': o1.offset().top + o1.height() + o1_yChange
  };
  const o2D = {
    'left': o2.offset().left,
    'right': o2.offset().left + o2.width(),
    'top': o2.offset().top,
    'bottom': o2.offset().top + o2.height()
  };
  // Adapted from https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection
  if (o1D.left < o2D.right &&
      o1D.right > o2D.left &&
      o1D.top < o2D.bottom &&
      o1D.bottom > o2D.top) {
    // collision detected!
    return true;
  }
  return false;
}

// Get random number between min and max integer
function getRandomNumber(min, max) {
  return (Math.random() * (max - min)) + min;
}
